package gloader

import (
	"context"
	"fmt"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"
	"sync"
	"wzgames/internal/constant"
	"wzgames/internal/model/agentp"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/mcomm"
	"wzgames/internal/response"
	"wzgames/pkg/logger"
	"wzgames/pkg/remote/etcd"
	"wzgames/pkg/utils/stringutil"
)

type GameManager struct {
	mu sync.RWMutex

	stopCh chan struct{}
	evtch  chan *etcd.KvEvt
	// 系统游戏
	needSysGame bool
	sysGames    map[int32]*gamep.WzGame
	// B端列表
	needAgents bool
	agents     map[int64]*GAgent
	// 游戏实例
	needAviator  bool
	needG8       bool
	needRoulette bool
	gameInsts    map[int64]*gamep.GameInst
	// 游戏实例变更监听
	aviatorInstListen  func(int64, etcd.KvEvtType)
	g8InstListen       func(int64, etcd.KvEvtType)
	rouletteInstListen func(int64, etcd.KvEvtType)
	// 单钱包 未开发
	needB4G bool
	b4Gs    map[int64]*agentp.AgentFromB

	gameDir  string
	agentDir string
	b4gDir   string
}

func newGameManager() *GameManager {
	r := &GameManager{
		evtch:     make(chan *etcd.KvEvt, 64),
		stopCh:    make(chan struct{}, 1),
		sysGames:  make(map[int32]*gamep.WzGame),
		agents:    make(map[int64]*GAgent),
		gameInsts: make(map[int64]*gamep.GameInst),
		b4Gs:      make(map[int64]*agentp.AgentFromB),
		gameDir:   fmt.Sprintf("%s/games", constant.CcBaseRoot),
		agentDir:  fmt.Sprintf("%s/agents", constant.CcBaseRoot),
	}

	return r
}

func (gmr *GameManager) ListenChange() {
	go gmr.onData()
}

func (gmr *GameManager) WatchBasePath() error {
	err := CClient().Watch(context.Background(), constant.CcBaseRoot, true, gmr.evtch)
	if err != nil {
		logger.Error("[GLoader] watch node /wzgames_base/base", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) WatchAviatorInsts() error {
	err := CClient().Watch(context.Background(), constant.CcInstanceAviator, true, gmr.evtch)
	if err != nil {
		logger.Error("[GLoader] watch node /wzgames_base/instants/aviator", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) WatchG8Insts() error {
	err := CClient().Watch(context.Background(), constant.CcInstanceG8, true, gmr.evtch)
	if err != nil {
		logger.Error("[GLoader] watch node /wzgames_base/instants/g8", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) WatchRouletteInsts() error {
	err := CClient().Watch(context.Background(), constant.CcInstanceRoulette, true, gmr.evtch)
	if err != nil {
		logger.Error("[GLoader] watch node /wzgames_base/instants/roulette", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) WatchB4G() error {
	err := CClient().Watch(context.Background(), constant.CcB4GRoot, true, gmr.evtch)
	if err != nil {
		logger.Error("[GLoader] watch node /wzgames_base/b4g", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) LoadSysGame(need bool) error {
	gmr.needSysGame = need
	err := CClient().GetRawValues(gmr.gameDir, func(s string, data []byte) error {
		if s == gmr.gameDir || data == nil {
			return nil
		}
		return gmr.parseSysGame(data)
	})
	if err != nil {
		logger.Error("[GLoader] load sys games fail", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) parseSysGame(b []byte) error {
	game := &gamep.WzGame{}
	err := proto.Unmarshal(b, game)
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return err
	}
	gmr.mu.Lock()
	gmr.sysGames[game.GameID] = game
	gmr.mu.Unlock()

	return nil
}

func (gmr *GameManager) LoadAgents(need bool) error {
	gmr.needAgents = need
	err := CClient().GetRawValues(gmr.agentDir, func(s string, data []byte) error {
		if s == gmr.agentDir || data == nil {
			return nil
		}
		return gmr.parseAgent(data)
	})
	if err != nil {
		logger.Error("[GLoader] load agents fail", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) parseAgent(b []byte) error {
	agent := &agentp.Agent{}
	err := proto.Unmarshal(b, agent)
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return err
	}
	gmr.mu.Lock()
	if agt, ok := gmr.agents[agent.AgtID]; ok {
		agt.SetAgent(agent)
	} else {
		gmr.agents[agent.AgtID] = NewAgent(agent)
	}
	gmr.mu.Unlock()

	return nil
}

func (gmr *GameManager) LoadAviatorInst(need bool, listener func(int64, etcd.KvEvtType)) error {
	gmr.needAviator = need
	gmr.aviatorInstListen = listener
	err := CClient().GetRawValues(constant.CcInstanceAviator, func(s string, data []byte) error {
		if s == constant.CcInstanceAviator || data == nil {
			return nil
		}
		_, e := gmr.parseGameInst(data)
		return e
	})
	if err != nil {
		logger.Error("[GLoader] load aviator instants fail", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) parseGameInst(b []byte) (int64, error) {
	inst := &gamep.GameInst{}
	err := proto.Unmarshal(b, inst)
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return 0, err
	}
	gmr.mu.Lock()
	gmr.gameInsts[inst.InstID] = inst
	gmr.mu.Unlock()

	return inst.InstID, nil
}

func (gmr *GameManager) LoadG8Inst(need bool, listener func(int64, etcd.KvEvtType)) error {
	gmr.needG8 = need
	gmr.g8InstListen = listener
	err := CClient().GetRawValues(constant.CcInstanceG8, func(s string, data []byte) error {
		if s == constant.CcInstanceG8 || data == nil {
			return nil
		}
		_, e := gmr.parseGameInst(data)
		return e
	})
	if err != nil {
		logger.Error("[GLoader] load g8 instants fail", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) LoadRouletteInst(need bool, listener func(int64, etcd.KvEvtType)) error {
	gmr.needRoulette = need
	gmr.rouletteInstListen = listener
	err := CClient().GetRawValues(constant.CcInstanceRoulette, func(s string, data []byte) error {
		if s == constant.CcInstanceRoulette || data == nil {
			return nil
		}
		_, e := gmr.parseGameInst(data)
		return e
	})
	if err != nil {
		logger.Error("[GLoader] load roulette instants fail", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) LoadRouletteInstById(instId int64) []byte {
	key := constant.CcInstanceRoulette + "/roulette_" + strconv.FormatInt(instId, 10)
	data, err := CClient().GetRawValue(key)
	if err != nil {
		logger.Error("[GLoader] load roulette instant fail", zap.Int64("InstId", instId), zap.Error(err))
		return nil
	}

	return data
}

func (gmr *GameManager) LoadB4G(need bool) error {
	gmr.needB4G = need
	err := CClient().GetRawValues(constant.CcB4GRoot, func(s string, data []byte) error {
		if s == constant.CcB4GRoot || data == nil {
			return nil
		}
		return gmr.parseB4G(data)
	})
	if err != nil {
		logger.Error("[GLoader] load B 4 G fail", zap.Error(err))
		return err
	}

	return nil
}

func (gmr *GameManager) parseB4G(b []byte) error {
	item := &agentp.AgentFromB{}
	err := proto.Unmarshal(b, item)
	if err != nil {
		//logger.Error("err == ", zap.String("error", err.Error()))
		return err
	}
	gmr.mu.Lock()
	gmr.b4Gs[item.AgtID] = item
	gmr.mu.Unlock()

	return nil
}

func (gmr *GameManager) onData() {
	for {
		select {
		case e := <-gmr.evtch:
			switch e.Type {
			case etcd.KvEventNew, etcd.KvEventUpdate:
				if strings.HasPrefix(e.Key, gmr.gameDir) && gmr.needSysGame {
					err := gmr.parseSysGame(e.Value)
					if err != nil {
						logger.Error("[GLoader] update sys game fail on listener", zap.String("Dir", e.Key), zap.Error(err))
					}
				} else if strings.HasPrefix(e.Key, gmr.agentDir) && gmr.needAgents {
					err := gmr.parseAgent(e.Value)
					if err != nil {
						logger.Error("[GLoader] update agent fail on listener", zap.String("Dir", e.Key), zap.Error(err))
					}
				} else if strings.HasPrefix(e.Key, constant.CcInstanceAviator) && gmr.needAviator {
					id, err := gmr.parseGameInst(e.Value)
					if err != nil {
						logger.Error("[GLoader] update aviator instance on listener", zap.String("Dir", e.Key), zap.Error(err))
					} else if gmr.aviatorInstListen != nil {
						gmr.aviatorInstListen(id, e.Type)
					}
				} else if strings.HasPrefix(e.Key, constant.CcInstanceG8) && gmr.needG8 {
					id, err := gmr.parseGameInst(e.Value)
					if err != nil {
						logger.Error("[GLoader] update g8 instance on listener", zap.String("Dir", e.Key), zap.Error(err))
					} else if gmr.g8InstListen != nil {
						gmr.g8InstListen(id, e.Type)
					}
				} else if strings.HasPrefix(e.Key, constant.CcInstanceRoulette) && gmr.needRoulette {
					id, err := gmr.parseGameInst(e.Value)
					if err != nil {
						logger.Error("[GLoader] update roulette instance on listener", zap.String("Dir", e.Key), zap.Error(err))
					} else if gmr.rouletteInstListen != nil {
						gmr.rouletteInstListen(id, e.Type)
					}
				} else if strings.HasPrefix(e.Key, constant.CcB4GRoot) && gmr.needB4G {
					err := gmr.parseB4G(e.Value)
					if err != nil {
						logger.Error("[GLoader] update B 4 G fail on parse", zap.String("Dir", e.Key), zap.Error(err))
					}
				}
			case etcd.KvEventDelete:
				if strings.HasPrefix(e.Key, constant.CcInstanceAviator) && gmr.needAviator {
					k := strings.Replace(e.Key, fmt.Sprintf("%s/", constant.CcInstanceAviator), "", 1)
					// v.GameTag + "_" + InstID
					items := strings.Split(k, "_")
					instId, _ := stringutil.ToInt64(items[1], 0)
					if gmr.aviatorInstListen != nil && instId > 0 {
						gmr.aviatorInstListen(instId, e.Type)
					}
				} else if strings.HasPrefix(e.Key, constant.CcInstanceG8) && gmr.needG8 {
					k := strings.Replace(e.Key, fmt.Sprintf("%s/", constant.CcInstanceG8), "", 1)
					// v.GameTag + "_" + InstID
					items := strings.Split(k, "_")
					instId, _ := stringutil.ToInt64(items[1], 0)
					if gmr.g8InstListen != nil && instId > 0 {
						gmr.g8InstListen(instId, e.Type)
					}
				} else if strings.HasPrefix(e.Key, constant.CcInstanceRoulette) && gmr.needRoulette {
					k := strings.Replace(e.Key, fmt.Sprintf("%s/", constant.CcInstanceRoulette), "", 1)
					items := strings.Split(k, "_")
					instId, _ := stringutil.ToInt64(items[1], 0)
					if gmr.rouletteInstListen != nil && instId > 0 {
						gmr.rouletteInstListen(instId, e.Type)
					}
				} else if strings.HasPrefix(e.Key, constant.CcB4GRoot) && gmr.needB4G {
					k := strings.Replace(e.Key, fmt.Sprintf("%s/", constant.CcB4GRoot), "", 1)
					items := strings.Split(k, "-")
					itemId, _ := stringutil.ToInt64(items[1], 0)
					gmr.mu.Lock()
					delete(gmr.b4Gs, itemId)
					gmr.mu.Unlock()
				}
			}
		case <-gmr.stopCh:
			return
		}
	}
}

func (gmr *GameManager) Destroy() {
	gmr.stopCh <- struct{}{}
	CClient().UnWatch(constant.CcBaseRoot)
	CClient().UnWatch(constant.CcInstanceAviator)
	CClient().UnWatch(constant.CcInstanceG8)
	CClient().UnWatch(constant.CcInstanceRoulette)
	CClient().UnWatch(constant.CcB4GRoot)
}

func (gmr *GameManager) LoadAgent(agtId int64) *GAgent {
	gmr.mu.RLock()
	agt, ok := gmr.agents[agtId]
	gmr.mu.RUnlock()
	if ok {
		return agt
	}

	return nil
}

func (gmr *GameManager) CheckGameInst(instId int64, curr string) (int32, int64, int16) {
	gmr.mu.RLock()
	inst, ok := gmr.gameInsts[instId]
	gmr.mu.RUnlock()

	if !ok || inst.Curr2GcID == nil {
		return 0, 0, response.ErrBadRequest
	}

	sysCurr := CommMgr().GetCurrency(curr)
	if sysCurr == nil {
		return 0, 0, response.ErrCurrency
	}
	gmr.mu.RLock()
	gcId, ok1 := inst.Curr2GcID[sysCurr.CurrID]
	gmr.mu.RUnlock()
	if ok1 {
		return inst.GameID, gcId, 0
	}

	return 0, 0, response.ErrCurrency
}

func (gmr *GameManager) CheckSysGameAndLang(gameId int32, lang string) bool {
	gmr.mu.RLock()
	v, ok := gmr.sysGames[gameId]
	if ok && v.Status == mcomm.Status_Up {
		if v.Langs == nil {
			gmr.mu.RUnlock()
			return false
		}
		_, ok1 := v.Langs[lang]
		gmr.mu.RUnlock()
		if ok1 {
			return true
		}
		return false
	}
	gmr.mu.RUnlock()

	return false
}

func (gmr *GameManager) InitGameInst(grp int32, fn func(inst *gamep.GameInst) error) error {
	gmr.mu.RLock()
	defer gmr.mu.RUnlock()
	for _, gmv := range gmr.gameInsts {
		if gmv.Grp != grp {
			continue
		}
		err := fn(gmv)
		if err != nil {
			//logger.Error("err == ", zap.String("error", err.Error()))
			return err
		}
	}
	return nil
}

func (gmr *GameManager) GetGameInst(instId int64) *gamep.GameInst {
	gmr.mu.RLock()
	if v, ok := gmr.gameInsts[instId]; ok {
		gmr.mu.RUnlock()
		return v
	}
	gmr.mu.RUnlock()
	return nil
}
